<?php

namespace {
    include './FourInALinePlayer.php';

    class DankerBotPlayer extends FourInALinePlayer {

        private $board;
        private $stateManager;
        private $init = false;
        private $stateMachine;

        private function init() {
            $moveChecker = new \DankerBot\MoveChecker();
            $this->board = new \DankerBot\Board(array(), $this, $moveChecker);
            $stateChecker = new DankerBot\StateChecker();
            $this->stateManager = new \DankerBot\StateManager($stateChecker);
            $this->stateMachine = new \DankerBot\StateMachine($this, $this->board, $moveChecker);
        }

        public function getHeight() {
            return $this->height;
        }

        public function getWidth() {
            return $this->width;
        }

        public function getMustMove() {
            $move = $this->getBoard()->getMoveChecker()->isOneMoveToWin();
            if ($move !== false) {
                return $move;
            }

            $move = $this->getBoard()->getMoveChecker()->isOneEnemyMoveToWin();
            if ($move !== false) {
                return $move;
            }

            return false;
        }

        public function getCalculateMove() {

        }

        public function getMoveByStrategy() {

        }

        public function getMove(array $grid) {
            if ($this->init == false) {
                $this->init = true;
                $this->init();
            }
            $this->board->setGrid($grid);

            $move = $this->getMustMove();
            if ($move !== false) {
                return $move;
            }

            //TODO remove
            $move = false;
            $counter = 0;

            while($move == false && $counter++ < 100){
                $move = mt_rand(0, $this->width - 1);
                if(!isset($grid[$move][$this->getBoard()->getHeight() - 1])){
                    $temp = $grid;
                    $this->stateMachine->addMove($temp, $move);
                    $this->getBoard()->setGrid($temp);
                    if($this->getBoard()->getMoveChecker()->isOneEnemyMoveToWin()){
                        $move = false;
                        $this->getBoard()->setGrid($grid);
                        continue;
                    }
                    return $move;
                }
                $move = false;
            }
            return $move;
        }

        public function getName() {
            return "Danker's eliminator";
        }

        public function getBoard() {
            return $this->board;
        }

        public function getStateManager() {
            return $this->stateManager;
        }

        public function getCurrentPlayerNumber() {
            return $this->currentPlayer;
        }

        public function getEnemeyPlayerNumber(){
            $current = $this->getCurrentPlayerNumber();
            $enemy = ($current == \FourInALinePlayer::PLAYER_ONE) ? \FourInALinePlayer::PLAYER_TWO
                        : \FourInALinePlayer::PLAYER_ONE;
            return $enemy;
        }

    }

}

namespace DankerBot {

    abstract class Strategy {

        public function analyse(Board $board) {

        }

        public function run(Strategy $strategy) {

        }

    }

    class Board {

        private $grid;
        private $player;
        private $moveChecker;
        private $width;
        private $height;
        static protected $_displayValues = array(\FourInALinePlayer::PLAYER_ONE => 'X',
            \FourInALinePlayer::PLAYER_TWO => 'O',
            \ConnectFour::DISC_EMPTY => '.');

        public function __construct(array $grid, \DankerBotPlayer $player,
                MoveChecker $checker) {
            $this->grid = $grid;
            $this->moveChecker = $checker;
            $this->player = $player;
            $this->moveChecker->setBoard($this);
            $this->width = $this->getPlayer()->getWidth();
            $this->height = $this->getPlayer()->getHeight();
        }

        public function getGrid() {
            return $this->grid;
        }

        public function setGrid(array $grid) {
            $this->grid = $grid;
        }

        public function getMoveChecker() {
            return $this->moveChecker;
        }

        public function getWidth() {
            return $this->width;
        }

        public function getHeight() {
            return $this->width;
        }

        /**
         *
         * @return DankerBotPlayer
         */
        public function getPlayer() {
            return $this->player;
        }

        public function getDisplayGrid() {
            $displayGrid = "";
            for ($i = ($this->height - 1); $i >= 0; $i--) {
                $displayGrid .= "\n    ";
                for ($j = 0; $j < $this->width; $j++) {
                    $char = $this->grid[$j][$i];
                    $displayGrid .= self::getDiscDisplayValue($char) . " ";
                }
            }
            $displayGrid.= "\n";
            return $displayGrid;
        }

        /**
         * Convert the internal disc value to a display value
         *
         * @param int $discValue
         * @return string the display value
         */
        protected static function getDiscDisplayValue($discValue) {
            $displayValue = self::$_displayValues[$discValue];

            if (!isset($displayValue)) {
                $displayValue = $discValue;
            }

            return $displayValue;
        }

    }

    class State {

        private $grid;
        private $status;
        private $previous;

        private $column;

        private $player;

        public function __construct(array $grid, State $previous, $column, $player) {
            $this->grid = $grid;
            $this->previous = $previous;
            $this->column = $column;
            $this->player = $player;
        }

        public function getPrevious() {
            return $this->previous;
        }

        public function getStatus() {
            return $this->status;
        }

        public function setStatus(Status $status) {
            $this->status = $status;
        }

        protected function setGrid(array $grid) {
            $this->grid = $grid;
        }

        public function getGrid(){
            return $this->grid;
        }

        public function getColumn(){
            return $this->column;
        }

        /**
         * Returns player number that made move
         * @return Integer
         */
        public function getPlayer(){
            return $this->player;
        }

    }

    class Status {

        private $status;

        const DRAW = 1;
        const LOSS = 0;
        const WIN = 2;

        public function __construct($status) {
            if (!\in_array($status, array(self::DRAW, self::LOSS, self::WIN))) {
                throw new \Exception("Wrong status");
            }
            $this->status = $status;
        }

        public function getStatus() {
            return $this->status;
        }

    }

    class StateManager {

        /**
         *
         * @var ArrayObject
         */
        private $states;
        /**
         * @var StateChecker
         */
        private $stateChecker;

        public function __construct(StateChecker $stateChecker) {
            $array = array();
            $this->states = new \ArrayObject($array);
            $this->stateChecker = $stateChecker;
        }

        public function addState(State $state) {
            $this->states = $state;
        }

        public function getStates() {
            return $this->states;
        }

        public function clear(){
            $array = array();
            $this->states = new \ArrayObject($array);
        }

        public function setStateChecker(StateChecker $checker) {
            $this->stateChecker = $checker;
        }

        /**
         *
         * @return StateChecker
         */
        public function getStateChecker() {
            return $this->stateChecker;
        }

    }

    class StateChecker {

        private $board;

        public function __construct() {

        }

        public function setBoard(Board $board) {
            $this->board = $board;
        }

        public function getBoard() {
            return $this->board;
        }

        public function isEnemyWinner() {
            $check = $this->check();
            if ($check == false) {
                return false;
            }
            if ($check != $this->getBoard()->getPlayer()->getCurrentPlayerNumber()) {
                return true;
            }
            return false;
        }

        public function isWinner() {
            $check = $this->check();
            if ($check == false) {
                return false;
            }
            if ($check == $this->getBoard()->getPlayer()->getCurrentPlayerNumber()) {
                return true;
            }
            return false;
        }

        public function isDraw() {
            $check = $this->check();
            if ($check == false) {
                return true;
            }
            return false;
        }

        public function isBoardFull() {
            $grid = $this->getBoard()->getGrid();

            for ($i = 0; $i < $this->getBoard()->getWidth(); $i++) {
                if ($grid[$this->getBoard()->getHeight() - 1][$i] == \ConnectFour::DISC_EMPTY) {
                    return false;
                }
            }

            return true;
        }

        public function check() {

            $width = $this->getBoard()->getWidth();
            $height = $this->getBoard()->getHeight();
            $grid = $this->getBoard()->getGrid();

            for ($i = 0; $i < $width; $i++) {
                // each row
                for ($j = 0; $j < $height; $j++) {

                    if ($grid[$i][$j] === \ConnectFour::DISC_EMPTY)
                        continue;

                    if ($this->horizontal($i, $j))
                        return $grid[$i][$j];

                    if ($this->vertical($i, $j))
                        return $grid[$i][$j];

                    if ($this->diagonal($i, $j))
                        return $grid[$i][$j];
                }
            }
            return false;
        }

        protected function horizontal($column, $row) {
            $width = $this->getBoard()->getWidth();
            $height = $this->getBoard()->getHeight();
            $grid = $this->getBoard()->getGrid();
            if (!isset($grid[$column + 3][$row]))
                return false;

            if ($grid[$column + 1][$row] === $grid[$column][$row] &&
                    $grid[$column + 2][$row] === $grid[$column][$row] &&
                    $grid[$column + 3][$row] === $grid[$column][$row]) {

                return true;
            }
            return false;
        }

        protected function vertical($column, $row) {
            $width = $this->getBoard()->getWidth();
            $height = $this->getBoard()->getHeight();
            $grid = $this->getBoard()->getGrid();
            if (!isset($grid[$column][$row + 3]))
                return false;

            if ($grid[$column][$row + 1] === $grid[$column][$row] &&
                    $grid[$column][$row + 2] === $grid[$column][$row] &&
                    $grid[$column][$row + 3] === $grid[$column][$row]) {

                return true;
            }
            return false;
        }

        protected function diagonal($column, $row) {
            $width = $this->getBoard()->getWidth();
            $height = $this->getBoard()->getHeight();
            $grid = $this->getBoard()->getGrid();
            // ascending
            if (isset($grid[$column + 3][$row + 3])) {
                if ($grid[$column + 1][$row + 1] === $grid[$column][$row] &&
                        $grid[$column + 2][$row + 2] === $grid[$column][$row] &&
                        $grid[$column + 3][$row + 3] === $grid[$column][$row]) {

                    return true;
                }
            }

            // descending
            if (isset($grid[$column + 3][$row - 3])) {
                if ($grid[$column + 1][$row - 1] === $grid[$column][$row] &&
                        $grid[$column + 2][$row - 2] === $grid[$column][$row] &&
                        $grid[$column + 3][$row - 3] === $grid[$column][$row]) {

                    return true;
                }
            }
            return false;
        }

    }

    class StateMachine {

        /**
         *
         * @var MoveChecker
         */
        private $moveChecker;

        /**
         * @var Player
         */
        private $player;

        private $board;

        private $startingGrid;

        private $counter;

        private $depth;

        private $stateChecker;

        public function __construct(\DankerBotPlayer $player, Board $board, MoveChecker $moveChecker){

            if($moveChecker->getBoard() !== $board){
                throw new \Exception("Board should be the same");
            }

            $this->moveChecker = $moveChecker;
            $this->player = $player;
            $this->board = $board;

            $this->stateChecker = new StateChecker();
            $this->stateChecker->setBoard($board);

        }

        public function getMoveChecker(){
            return $this->moveChecker;
        }

        public function getCounter(){
            return $this->counter;
        }

        public function getDepth(){
            return $this->depth;
        }

        public function getBoard(){
            return $this->board;
        }

        /**
         *
         * @param array $grid
         * @return Array Available columns for insertion
         */
        public function getAvailableMoves(array $grid){
            $rez = array();
            $height = $this->getBoard()->getHeight() - 1;
            $width = $this->getBoard()->getWidth();
            for($i = 0; $i < $width; $i++){
                if($grid[$i][$height] == \ConnectFour::DISC_EMPTY){
                    $rez[] = $i;
                }
            }

            return $rez;
        }

        /**
         * Assumes that move is valid because we got it from available moves
         * @param Array $grid
         * @param Integer $column
         * @return Bool Success value
         */
        public function addMove(&$grid, $column){
            return $this->addPlayerMove($this->getPlayer()->getCurrentPlayerNumber(), $grid, $column);
        }

        public function addEnemyMove(&$grid, $column){
            return $this->addPlayerMove($this->getPlayer()->getEnemeyPlayerNumber(), $grid, $column);
        }

        protected function addPlayerMove($player, &$grid, $column){
            $col = $grid[$column];
            for($i = 0; $i < count($col); $i++){
                if($col[$i] == \ConnectFour::DISC_EMPTY){
                    $col[$i] = $player;
                    break;
                    return true;
                }
            }
            return false;
        }

        /**
         * Returns false if there is not good moves available and number if there is
         * @return Mixed
         */
        public function getMove(){
            $this->startingGrid = $this->getBoard()->getGrid();

            $action = true;
            $grid = $this->startingGrid;
            $counter = 0;
            $endMove = false;
            $previousState = null;
            $depth = 0;

            while($action){
                $moves = $this->getAvailableMoves($grid);
                foreach($moves as $column){
                    $this->addMove($grid, $column);
                    $state = new State($grid, $previousState, $column, $this->getPlayer()->getCurrentPlayerNumber());
                    $this->getBoard()->setGrid($state->getGrid());

                    if($this->getMoveChecker()->isOneEnemyMoveToWin() === false){
                        $rez = $this->getStateMove($state);

                    }

                    $this->counter++;
                }
                if($this->counter > 10000){
                    break;
                }
            }

            $this->getBoard()->setGrid($this->startingGrid);
            return $endMove;
        }

        protected function getStateMove(State $state){
            $this->counter++;


        }

        public function getPlayer(){
            return $this->player;
        }
    }

    class MoveChecker {

        private $board;

        public function __construct() {

        }

        public function setBoard(Board $board) {
            $this->board = $board;
        }

        public function getBoard() {
            return $this->board;
        }

        public function isOneMoveToWin() {
            return $this->checkOneMove($this->getBoard()->getPlayer()->getCurrentPlayerNumber());
        }

        public function isOneEnemyMoveToWin() {
            $enemy = $this->getBoard()->getPlayer()->getEnemeyPlayerNumber();
            return $this->checkOneMove($enemy);
        }

        public function checkOneMove($player) {

            $width = $this->getBoard()->getPlayer()->getWidth();
            $height = $this->getBoard()->getPlayer()->getHeight();


            // $i - columns
            // $j - rows
            $grid = $this->getBoard()->getGrid();
            for ($i = 0; $i < $width; $i++) {
                // each row
                for ($j = 0; $j < $height; $j++) {

                    if ($i < $width - 3) {
                        $val = $this->horizontal($i, $j, $player);
                        if ($val !== false)
                            return $val;
                    }
                    if ($j < $height - 3) {
                        $val = $this->vertical($i, $j, $player);
                        if ($val !== false)
                            return $val;
                    }
                    if ($i < $width - 3 && $j < $height - 3) {
                        $val = $this->diagonal($i, $j, $player);
                        if ($val !== false)
                            return $val;
                    }
                    if ($i >= 3 && $j < $height - 3) {
                        $val = $this->diagonal($i, $j, $player, -1);
                        if ($val !== false)
                            return $val;
                    }
                }
            }
            return false;
        }

        protected function horizontal($column, $row, $player) {
            $grid = $this->getBoard()->getGrid();

            $width = $this->getBoard()->getPlayer()->getWidth();
//            echo __METHOD__ . "\n";
            $empty = 0;
            $col = 0;
            for ($i = 0; $i <= 3; $i++) {
                if ($grid[$column + $i][$row] === \ConnectFour::DISC_EMPTY) {
                    if (++$empty == 2) {
                        return false;
                    }
                    $col = $column + $i;
                } else if ($grid[$column + $i][$row] != $player) {
                    return false;
                }
            }
            return $col;
        }

        protected function vertical($column, $row, $player) {
            $grid = $this->getBoard()->getGrid();

            $height = $this->getBoard()->getPlayer()->getHeight();

            $count = 0;
//            echo __METHOD__ . "\n";

            if (($row + 3) >= $this->getBoard()->getHeight() ||
                    ($grid[$column][$row + 3] !== \ConnectFour::DISC_EMPTY)) {
                return false;
            }
            for ($i = 0; $i < 3; $i++) {
                if ($grid[$column][$row + $i] === \ConnectFour::DISC_EMPTY) {
                    return false;
                } else if ($grid[$column][$row + $i] == $player) {
                    if ((++$count) == 3) {
                        return $column;
                    }
                } else {
                    return false;
                }
            }
            //echo "boo";
            return false;
        }

        protected function diagonal($column, $row, $player, $direction = 1) {
            // ascending
            $grid = $this->getBoard()->getGrid();
            $width = $this->getBoard()->getPlayer()->getWidth();
            $height = $this->getBoard()->getPlayer()->getHeight();
            //echo __METHOD__ . " col: $column, row: $row, dir $direction\n";


            $empty = 0;
            $col = 0;

            $changed = false;

            if(($column + 3*$direction) < 0){
                return false;
            }
            if (($column + 3*$direction) >= $this->getBoard()->getWidth() ||
                    ($row + 3) >= $this->getBoard()->getHeight()) {
                return false;
            }


            for ($i = 0; $i <= 3; $i++) {
                //echo "I: $i\n";

                $i *= $direction;

                if ($grid[$column + $i][$row + abs($i)] == \ConnectFour::DISC_EMPTY) {
                    $val = $grid[$column + $i][$row + abs($i)];
                    if (++$empty == 2) {
                        return false;
                    }
                    //check if column below is filled

                    $filled = true;
                    $set = $row + abs($i) - 1;

                    if ($set >= 0) {
                        if ($grid[$column + $i][$set] == \ConnectFour::DISC_EMPTY) {
                            $filled = false;
                        }
                    }

                    if ($filled == true) {
                        //echo "cc: $column, i: $i\n";
                        $col = $column + $i;
                        $changed = true;
                    }
                } else if ($grid[$column + $i][$row + abs($i)] != $player) {
                    return false;
                }

                $i = abs($i);
            }

            if (!$changed) {
                return false;
            }
            //echo "return col: $col\n";
            return $col;
        }

    }

}